package com.bank.service.impl;

import com.bank.constant.AccountStatus;
import com.bank.constant.MessageConstant;
import com.bank.constant.TransactionType;
import com.bank.dto.AccountOperationDTO;
import com.bank.entity.Account;
import com.bank.entity.Bill;
import com.bank.entity.BillTransaction;
import com.bank.exception.*;
import com.bank.mapper.AccountMapper;
import com.bank.mapper.BillMapper;
import com.bank.mapper.BillTransactionMapper;
import com.bank.service.BillService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Service
public class BillServiceImpl implements BillService {
    @Autowired
    private BillMapper billMapper;
    @Autowired
    private AccountMapper accountMapper;
    @Autowired
    private BillTransactionMapper billTransactionMapper;

    @Override
    public Bill getBillByAccountId(Long accountId) {
        return billMapper.getBillByAccountId(accountId);
    }

    @Transactional
    @Override
    public void addBillByAccountId(BigDecimal balance, Long accountId) {
        Bill bill = Bill.builder()
                .accountId(accountId)
                .balance(balance)
                .build();
        billMapper.addBillByAccountId(bill);
    }

    @Transactional
    @Override
    public void changeBalance(AccountOperationDTO operationDTO) {
        Account account = accountMapper.getAccountById(operationDTO.getAccountId());
        Bill bill = billMapper.getBillByAccountId(operationDTO.getAccountId());

        // 1. 判断账户是否存在
        if (account == null) {
            throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        // 2. 验证账户状态（正常或挂失可以操作）
        if (!AccountStatus.NORMAL.equals(account.getStatus()) && !AccountStatus.LOST.equals(account.getStatus())) {
            throw new AccountStatusInvalidException(MessageConstant.ACCOUNT_STATUS_INVALID);
        }

        Integer type = operationDTO.getAmount().compareTo(BigDecimal.ZERO) > 0 ? TransactionType.DEPOSIT : TransactionType.WITHDRAWAL;
        if (operationDTO.getRelatedAccount() != null) type = TransactionType.TRANSFER;
        // 3. 查询是否超过每月最大限额
        // 取款，转账是否超过每月最大限额
        if (type.equals(TransactionType.WITHDRAWAL) || type.equals(TransactionType.TRANSFER)) {
            // 判断还有没有足够的余额
            if (bill.getBalance().compareTo(operationDTO.getAmount().negate()) < 0) {
                throw new AccountBalanceNotEnoughException(MessageConstant.ACCOUNT_BALANCE_NOT_ENOUGH);
            }

            // 本月消费了多少金额
            BigDecimal monthlyLimit = billTransactionMapper.getMaxLimitByAccountId(operationDTO, operationDTO.getOperationTime().format(DateTimeFormatter.ofPattern("yyyy-MM")));
            if (monthlyLimit != null &&
                    billMapper.getMonthlyLimit(account.getAccountId()).
                            compareTo(monthlyLimit.subtract(operationDTO.getAmount())) < 0) {
                throw new AccountMaxLimitException(MessageConstant.ACCOUNT_LIMIT_EXCEEDED);
            }
        }

        // 4. 验证密码
        if (!account.getPassword().equals(DigestUtils.md5DigestAsHex(operationDTO.getPassword().getBytes()))) {
            throw new AccountPasswordErrorException(MessageConstant.ACCOUNT_PASSWORD_ERROR);
        }

        // 5. 修改账户余额(加锁)
        bill.setBalance(bill.getBalance().add(operationDTO.getAmount()));
        account.setUpdateTime(LocalDateTime.now());
        synchronized (this) {
            // TODO: 需要修改
            billMapper.setBalanceByAccountId(bill);
            accountMapper.updateTimeByAccountId(account.getAccountId(), account.getUpdateTime());
        }

        // 6. 有无关联用户
        if (operationDTO.getRelatedAccount() != null) {
            // 转账金额不能为负数
            if (operationDTO.getAmount().compareTo(BigDecimal.ZERO) >= 0) {
                throw new AccountTransferNotAllowedException(MessageConstant.ACCOUNT_TRANSFER_NOT_LOWER_THAN_ZERO);
            }
            // 不能转账给自己
            if (account.getAccountId().equals(operationDTO.getRelatedAccount())) {
                throw new AccountTransferNotAllowedException(MessageConstant.ACCOUNT_TRANSFER_NOT_SELF);
            }

            Account relatedAccount = accountMapper.getAccountById(operationDTO.getRelatedAccount());
            Bill relatedBill = billMapper.getBillByAccountId(operationDTO.getRelatedAccount());
            // 转账账户是否存在
            if (relatedAccount == null) {
                throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
            }
            // 转账账户状态是否正常
            if (!AccountStatus.NORMAL.equals(relatedAccount.getStatus())) {
                throw new AccountStatusInvalidException(MessageConstant.ACCOUNT_STATUS_INVALID);
            }

            relatedBill.setBalance(relatedBill.getBalance().subtract(operationDTO.getAmount()));
            relatedAccount.setUpdateTime(LocalDateTime.now());
            synchronized (this) {
                billMapper.setBalanceByAccountId(relatedBill);
                accountMapper.updateTimeByAccountId(relatedAccount.getAccountId(), relatedAccount.getUpdateTime());
            }
        }

        // 7. 添加流水记录
        // TODO: 添加关联用户
        BillTransaction billTransaction = BillTransaction.builder()
                .accountId(account.getAccountId())
                .transactionType(type)
                .amount(operationDTO.getAmount())
                .balance(bill.getBalance())
                .transactionTime(LocalDateTime.now())
                .remark(operationDTO.getRemark())
                .relatedAccount(operationDTO.getRelatedAccount())
                .build();
        billTransactionMapper.addTransaction(billTransaction);
    }

    @Override
    public BigDecimal getBalanceByAccountId(Long accountId) {
        Bill bill = billMapper.getBillByAccountId(accountId);
        if (bill == null) {
            throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }
        return bill.getBalance();
    }
}
